package com.yiruantong.common.doc.config;

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Paths;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import lombok.RequiredArgsConstructor;
import com.yiruantong.common.core.utils.StringUtils;
import com.yiruantong.common.doc.config.properties.SpringDocProperties;
import com.yiruantong.common.doc.handler.OpenApiHandler;
import org.springdoc.core.configuration.SpringDocConfiguration;
import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
import org.springdoc.core.customizers.OpenApiCustomizer;
import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
import org.springdoc.core.properties.SpringDocConfigProperties;
import org.springdoc.core.providers.JavadocProvider;
import org.springdoc.core.service.OpenAPIService;
import org.springdoc.core.service.SecurityService;
import org.springdoc.core.utils.PropertyResolverUtils;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;

/**
 * Swagger 文档配置
 *
 * @author YiRuanTong
 */
@RequiredArgsConstructor
@AutoConfiguration(before = SpringDocConfiguration.class)
@EnableConfigurationProperties(SpringDocProperties.class)
@ConditionalOnProperty(name = "springdoc.api-docs.enabled", havingValue = "true", matchIfMissing = true)
public class SpringDocConfig {

  private final ServerProperties serverProperties;

  @Bean
  @ConditionalOnMissingBean(OpenAPI.class)
  public OpenAPI openApi(SpringDocProperties properties) {
    OpenAPI openApi = new OpenAPI();
    // 文档基本信息
    SpringDocProperties.InfoProperties infoProperties = properties.getInfo();
    Info info = convertInfo(infoProperties);
    openApi.info(info);
    // 扩展文档信息
    openApi.externalDocs(properties.getExternalDocs());
    openApi.tags(properties.getTags());
    openApi.paths(properties.getPaths());
    openApi.components(properties.getComponents());
    Set<String> keySet = properties.getComponents().getSecuritySchemes().keySet();
    List<SecurityRequirement> list = new ArrayList<>();
    SecurityRequirement securityRequirement = new SecurityRequirement();
    keySet.forEach(securityRequirement::addList);
    list.add(securityRequirement);
    openApi.security(list);

    return openApi;
  }

  private Info convertInfo(SpringDocProperties.InfoProperties infoProperties) {
    Info info = new Info();
    info.setTitle(infoProperties.getTitle());
    info.setDescription(infoProperties.getDescription());
    info.setContact(infoProperties.getContact());
    info.setLicense(infoProperties.getLicense());
    info.setVersion(infoProperties.getVersion());
    return info;
  }

  /**
   * 自定义 openapi 处理器
   */
  @Bean
  public OpenAPIService openApiBuilder(Optional<OpenAPI> openAPI,
                                       SecurityService securityParser,
                                       SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils,
                                       Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomisers,
                                       Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomisers, Optional<JavadocProvider> javadocProvider) {
    return new OpenApiHandler(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomisers, serverBaseUrlCustomisers, javadocProvider);
  }

  /**
   * 对已经生成好的 OpenApi 进行自定义操作
   */
  @Bean
  public OpenApiCustomizer openApiCustomizer() {
    String contextPath = serverProperties.getServlet().getContextPath();
    String finalContextPath;
    if (StringUtils.isBlank(contextPath) || "/".equals(contextPath)) {
      finalContextPath = "";
    } else {
      finalContextPath = contextPath;
    }
    // 对所有路径增加前置上下文路径
    return openApi -> {
      Paths oldPaths = openApi.getPaths();
      if (oldPaths instanceof PlusPaths) {
        return;
      }
      PlusPaths newPaths = new PlusPaths();
      oldPaths.forEach((k, v) -> newPaths.addPathItem(finalContextPath + k, v));
      openApi.setPaths(newPaths);
    };
  }

  /**
   * 单独使用一个类便于判断 解决springdoc路径拼接重复问题
   *
   * @author YiRuanTong
   */
  static class PlusPaths extends Paths {

    public PlusPaths() {
      super();
    }
  }

}
